﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.Collections.ObjectModel;
using System.Configuration;
using System.IO;
using System.ServiceModel;
using System.Collections;
using PSE.Framework.Threading;

namespace PSE.Framework.Process
{
    public class HierarchicalProcessManager : HierarchicalProcessManager<HierarchicalProcess, HierarchicalProcess, IProcessService, HierarchicalProcessManager>
    {
        protected override Uri GetServiceAddress()
        {
            return new Uri(string.Format("net.pipe://localhost/Process/{0}", this.ProcessId));
        }

        protected override IProcessService CreateServiceInstance()
        {
            return new ProcessService();
        }
    }

    public abstract class HierarchicalProcessManager<TParent, TChild, TProcessService, TSelf> : IHierarchicalProcessManager
        where TParent : class, IHierarchicalProcess, new()
        where TChild : class, IHierarchicalProcess, new()
        where TProcessService : class, IProcessService
        where TSelf : HierarchicalProcessManager<TParent, TChild, TProcessService, TSelf>, new()
    {
        protected HierarchicalProcessManager()
        {
            _children = new ObservableCollection<TChild>();
            _readOnlyChildren = new HierarchicalProcessCollection<TChild>(_children);
        }

        private static TSelf _instance = null;
        private static object _lock = new object();
        // Utiliza um CrossProcessManualResetEvent para o timeout do MarshalByRefObject não expirar nunca
        private CrossProcessManualResetEvent _exit = new CrossProcessManualResetEvent(false);
        private ServiceHost _processServiceHost = null;
        private TProcessService _processService = null;

        #region GetInstance
        public static TSelf GetInstance()
        {
            lock (_lock)
            {
                if (_instance == null)
                    _instance = new TSelf();
            }
            return _instance;
        }
        #endregion

        #region IsInitialized
        private bool _isInitialized = false;

        public bool IsInitialized
        {
            get { return _isInitialized; }
        }

        protected void CheckIsInitialized()
        {
            if (!this.IsInitialized)
                throw new InvalidOperationException("The process is not initialized");
        }
        #endregion

        #region Name
        private string _name = null;

        public string Name
        {
            get { return _name; }
        }
        #endregion

        #region ProcessId
        public int ProcessId
        {
            get { return System.Diagnostics.Process.GetCurrentProcess().Id; }
        }
        #endregion

        #region FileName
        public string FileName
        {
            get { return Path.Combine(System.Windows.Forms.Application.StartupPath, System.Diagnostics.Process.GetCurrentProcess().ProcessName); }
        }
        #endregion

        #region Domain
        public string Domain
        {
            get { return Environment.UserDomainName; }
        }
        #endregion

        #region UserName
        public string UserName
        {
            get { return Environment.UserName; }
        }
        #endregion

        #region ServiceAddress
        public Uri ServiceAddress
        {
            get { return this.GetServiceAddress(); }
        }
        #endregion

        #region Parent
        private TParent _parent;

        public TParent Parent
        {
            get { return _parent; }
        }

        IHierarchicalProcess IHierarchicalProcessManager.Parent
        {
            get { return this.Parent; }
        }
        #endregion

        #region Children
        private ObservableCollection<TChild> _children = null;
        private HierarchicalProcessCollection<TChild> _readOnlyChildren = null;

        public HierarchicalProcessCollection<TChild> Children
        {
            get { return _readOnlyChildren; }
        }

        IList IHierarchicalProcessManager.Children
        {
            get { return this.Children; }
        }
        #endregion

        #region Initialize
        public void Initialize(string name)
        {
            this.Initialize(name, null);
        }

        public virtual void Initialize(string name, ParentProcessStartInfo parentStartInfo)
        {
            if (string.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");
            if (this.IsInitialized)
                throw new InvalidOperationException("The process is already initialized");

            _name = name;

            // Criando instância do serviço do processo
            _processService = this.CreateServiceInstance();
            _processService.ParentReleased += new EventHandler<ProcessNameEventArgs>(_processService_ParentReleased);
            _processService.ChildClosed += new EventHandler(_processService_ChildClosed);

            // Criando hoster
            _processServiceHost = this.CreateServiceHost(_processService);

            // Abrindo hoster
            _processServiceHost.Open();

            // Verifica se tem processo pai
            if (parentStartInfo != null)
            {
                _parent = new TParent();
                _parent.Initialize(parentStartInfo, this);
            }

            _isInitialized = true;
        }

        private void _processService_ParentReleased(object sender, ProcessNameEventArgs e)
        {
            if (this.Children[e.ProcessName] != null)
                this.Children[e.ProcessName].ReleaseChild();
        }

        protected abstract Uri GetServiceAddress();
        protected abstract TProcessService CreateServiceInstance();

        protected virtual ServiceHost CreateServiceHost(TProcessService serviceInstance)
        {
            ServiceHost processServiceHost = new ServiceHost(serviceInstance, this.GetServiceAddress());

            processServiceHost.AddServiceEndpoint(typeof(TProcessService), new NetNamedPipeBinding(), this.GetServiceAddress());

            return processServiceHost;
        }

        #endregion

        #region Exit
        private void _processService_ChildClosed(object sender, EventArgs e)
        {
            this.Exit();
        }

        public void WaitForExit()
        {
            this.CheckIsInitialized();

            // Libera o processo pai para continuar
            if (this.Parent != null)
            {
                this.Parent.ReleaseParent();
            }

            // Espera até o processo ser finalizado
            _exit.WaitOne();
        }

        public void Exit()
        {
            this.CheckIsInitialized();

            // Libera o processo para ser finalizado
            _exit.Set();
        }
        #endregion

        #region IsChildStarted
        public bool IsChildStarted(string name)
        {
            this.CheckIsInitialized();

            if (string.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");

            return this.Children[name] != null && this.Children[name].IsStarted;
        }
        #endregion

        #region StartChild
        public TChild StartChild(string name, string fileName)
        {
            if (string.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");
            if (string.IsNullOrEmpty(fileName))
                throw new ArgumentNullException("fileName");

            return this.StartChild(new ChildProcessStartInfo() { Name = name, FileName = fileName });
        }

        IHierarchicalProcess IHierarchicalProcessManager.StartChild(string name, string fileName)
        {
            return this.StartChild(name, fileName);
        }

        public TChild StartChild(ChildProcessStartInfo childStartInfo)
        {
            this.CheckIsInitialized();

            if (childStartInfo == null)
                throw new ArgumentNullException("childStartInfo");
            if (string.IsNullOrEmpty(childStartInfo.Name))
                throw new ArgumentNullException("childStartInfo.Name");
            if (string.IsNullOrEmpty(childStartInfo.FileName))
                throw new ArgumentNullException("childStartInfo.FileName");

            if (this.Children[childStartInfo.Name] != null)
                throw new ArgumentException(string.Format("The child process name '{0}' already exists", childStartInfo.Name), "childStartInfo.Name");

            TChild child = new TChild();

            try
            {
                // Adiciona o processo na coleção, dessa forma o event handler "_processService_ParentReleased" 
                // pode encontrá-lo e chamar o método ReleaseChild, que indica que o processo filho foi iniciado 
                // com sucesso e o método Initialize pode continuar
                _children.Add(child);

                child.Initialize(childStartInfo, this);
            }
            catch
            {
                // Se ocorrer um erro dá Dipose no filho, que vai disparar o evento Closed e remove-lo da 
                // coleção Children
                child.Dispose();

                throw;
            }

            child.Restarted += new EventHandler(child_Restarted);
            child.Closed += new EventHandler(child_Closed);
            child.CheckIsAliveException += new EventHandler<ProcessExceptionEventArgs>(child_CheckIsAliveException);

            return child;
        }

        IHierarchicalProcess IHierarchicalProcessManager.StartChild(ChildProcessStartInfo childStartInfo)
        {
            return this.StartChild(childStartInfo);
        }

        private void child_Restarted(object sender, EventArgs e)
        {
            this.OnChildRestarted(new ProcessEventArgs((TChild)sender));
        }

        private void child_Closed(object sender, EventArgs e)
        {
            TChild child = (TChild)sender;

            if (_children.Contains(child))
            {
                child.Restarted -= new EventHandler(child_Restarted);
                child.Closed -= new EventHandler(child_Closed);
                child.CheckIsAliveException -= new EventHandler<ProcessExceptionEventArgs>(child_CheckIsAliveException);

                _children.Remove(child);
            }

            this.OnChildClosed(new ProcessEventArgs((TChild)sender));
        }

        private void child_CheckIsAliveException(object sender, ProcessExceptionEventArgs e)
        {
            this.OnCheckIsAliveException(e);
        }
        #endregion

        #region CloseChild
        public void CloseChild(string name)
        {
            this.CheckIsInitialized();

            if (string.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");
            if (this.Children[name] == null)
                throw new ArgumentException("The child process name '{0}' doesn't exist", "name");

            this.Children[name].Dispose();
        }

        public void CloseAllChildren()
        {
            this.CheckIsInitialized();

            for (int i = _children.Count - 1; i >= 0; i--)
            {
                _children[i].Dispose();
            }
        }
        #endregion

        #region ChildRestarted
        public event EventHandler<ProcessEventArgs> ChildRestarted;

        protected virtual void OnChildRestarted(ProcessEventArgs e)
        {
            if (ChildRestarted != null)
                ChildRestarted(this, e);
        }
        #endregion

        #region ChildClosed
        public event EventHandler<ProcessEventArgs> ChildClosed;

        protected virtual void OnChildClosed(ProcessEventArgs e)
        {
            if (ChildClosed != null)
                ChildClosed(this, e);
        }
        #endregion

        #region CheckIsAliveException
        public event EventHandler<ProcessExceptionEventArgs> CheckIsAliveException;

        protected virtual void OnCheckIsAliveException(ProcessExceptionEventArgs e)
        {
            if (CheckIsAliveException != null)
                CheckIsAliveException(this, e);
        }
        #endregion

        #region IDisposable Members

        public virtual void Dispose()
        {
            if (_processServiceHost != null)
            {
                ((IDisposable)_processServiceHost).Dispose();
                _processServiceHost = null;
            }

            if (_processService != null)
            {
                _processService.ParentReleased -= new EventHandler<ProcessNameEventArgs>(_processService_ParentReleased);
                _processService.ChildClosed -= new EventHandler(_processService_ChildClosed);
                _processService = null;
            }

            if (_parent != null)
            {
                _parent.Dispose();
                _parent = null;
            }

            for (int i = _children.Count - 1; i >= 0; i--)
            {
                _children[i].Dispose();
            }

            _name = null;

            _exit.Set();
            _exit.Reset();

            _isInitialized = false;
        }

        #endregion
    }
}
